﻿using System;
using System.Collections.Generic;
using System.Threading;
using TiaoTiaoCode.Common.ClassEntities;
using TiaoTiaoCode.Common.Enums;
using TiaoTiaoCode.Common.Extensions;
using TiaoTiaoCode.Common.Formatters;
using TiaoTiaoCode.Modbuser.Interfaces;
using TouchSocket.Core;
using TouchSocket.Sockets;

namespace TiaoTiaoCode.Modbuser.Modbusers
{
    /// <summary>
    /// ModbusTcp对象
    /// </summary>
    internal class TcpModbuser : ITcpModbuser, IDisposable
    {
        #region

        const int ProtocolHeadBytesLength = 8;

        #endregion

        #region 字段

        readonly AutoResetEvent _receiveLock;
        readonly TiaoTiaoIncrementCount _incrementCount;
        readonly TouchSocket.Sockets.TcpClient _tcpClient;
        readonly TiaoTiaoByteTransFormatter _byteFormatter;

        /// <summary>
        /// 是否接收数据并处理
        /// </summary>
        byte _needReceive = 0;

        /// <summary>
        /// 接收数据
        /// </summary>
        byte[] _receiveBuffer;


        Action<byte[]> _finishReceive;

        #endregion

        #region 属性

        /// <summary>
        /// 连接IP
        /// </summary>
        public string IP { get; private set; }

        /// <summary>
        /// 连接端口
        /// </summary>
        public ushort Port { get; private set; }

        /// <summary>
        /// 站号
        /// </summary>
        public byte Station { get; private set; }

        /// <summary>
        /// 字节流顺序
        /// </summary>
        public ByteDataFormatType DataFormat { get; private set; }

        /// <summary>
        /// 首地址是否为0
        /// </summary>
        public bool AddressStartWithZero { get; private set; }

        /// <summary>
        /// 字符串是否反转
        /// </summary>
        public bool IsStringReverse { get; private set; }

        /// <summary>
        /// 检查消息是否一致
        /// </summary>
        public bool CheckMessageId { get; private set; }

        /// <summary>
        /// short ushort 是否反转
        /// </summary>
        public bool IsInteger16Reverse { get; private set; }

        #endregion

        /// <summary>
        /// 构造函数
        /// </summary>
        public TcpModbuser()
        {
            IP = "127.0.0.1";
            Port = 502;
            Station = 1;
            DataFormat = ByteDataFormatType.CDAB;
            IsInteger16Reverse = true;

            _receiveLock = new AutoResetEvent(false);
            _incrementCount = new TiaoTiaoIncrementCount(int.MaxValue);
            _byteFormatter = new TiaoTiaoByteTransFormatter { IsStringReverseByteWord = false, DataFormat = ByteDataFormatType.CDAB };

            _tcpClient = new TouchSocket.Sockets.TcpClient();
            _tcpClient.Connected += TcpClientOnConnected;
            _tcpClient.Disconnected += TcpClientOnDisconnected;
            _tcpClient.Received += TcpClientOnReceived;
        }

        #region ITcpModbuser

        /// <summary>
        /// 设置连接
        /// </summary>
        /// <param name="ip">连接IP</param>
        /// <param name="port">连接端口</param>
        /// <returns></returns>
        public ITcpModbuser SetConnectInfo(string ip = "127.0.0.1", ushort port = 502)
        {
            IP = ip;
            Port = port;

            return this;
        }

        /// <summary>
        /// 设置站号
        /// </summary>
        /// <param name="station"></param>
        /// <returns></returns>
        public ITcpModbuser SetStation(byte station)
        {
            Station = station;

            return this;
        }

        /// <summary>
        /// 设置字节流顺序
        /// </summary>
        /// <param name="dataFormat"></param>
        /// <returns></returns>
        public ITcpModbuser SetByteDataFormat(ByteDataFormatType dataFormat)
        {
            DataFormat = dataFormat;

            return this;
        }

        /// <summary>
        /// 设置是否首地址为0
        /// </summary>
        /// <param name="addressStartWithZero"></param>
        /// <returns></returns>
        public ITcpModbuser SetAddressStartWithZero(bool addressStartWithZero)
        {
            AddressStartWithZero = addressStartWithZero;

            return this;
        }

        /// <summary>
        /// 设置字符串是否反转
        /// </summary>
        /// <param name="isStringReverse"></param>
        /// <returns></returns>
        public ITcpModbuser SetIsStringReverse(bool isStringReverse)
        {
            IsStringReverse = isStringReverse;

            return this;
        }

        /// <summary>
        /// 设置是否检查消息号一致
        /// </summary>
        /// <param name="checkMessageId"></param>
        /// <returns></returns>
        public ITcpModbuser SetCheckMessageId(bool checkMessageId)
        {
            CheckMessageId = checkMessageId;

            return this;
        }

        /// <summary>
        /// 读取连续的数据内容
        /// </summary>
        /// <typeparam name="T">限定只能是基础数据类型</typeparam>
        /// <param name="startAddress">开始读取的地址位</param>
        /// <param name="readLength">读取长度</param>
        /// <returns></returns>
        public T[] Read<T>(string startAddress, ushort readLength)
        {
            ConntectServer();

            var type = typeof(T);
            var typeCode = Type.GetTypeCode(type);
            var result = Read<T>(startAddress, readLength, typeCode);

            DisconnectServer();

            return result;
        }

        #endregion

        #region IModbuser

        /// <summary>
        /// 连接
        /// </summary>
        /// <returns></returns>
        public bool ConntectServer()
        {
            if (_tcpClient == null)
                throw new Exception("未创建客户端，不能连接");

            var cfg = new TouchSocketConfig();
            cfg.SetRemoteIPHost($"{IP}:{Port}")
                .SetBufferLength(1024);

            _tcpClient.Setup(cfg);
            _tcpClient.Connect();

            return _tcpClient.MainSocket.Connected;
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public void DisconnectServer()
        {
            _tcpClient.Close();
        }

        /// <summary>
        /// 读
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="addresses"></param>
        public T[] Read<T>(params string[] addresses)
        {
            ConntectServer();

            var type = typeof(T);
            var typeCode = Type.GetTypeCode(type);

            var result = new List<T>();

            foreach (var address in addresses)
            {
                var tmp = Read<T>(address, 1, typeCode);

                result.AddRange(tmp);
            }

            DisconnectServer();

            return result.ToArray();
        }

        /// <summary>
        /// 写
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="address"></param>
        /// <param name="value"></param>
        public bool Write<T>(string address, T value)
        {
            ConntectServer();

            var type = typeof(T);
            var typeCode = Type.GetTypeCode(type);

            switch (typeCode)
            {
                //case TypeCode.Boolean:
                //    {
                //        var bytes = ((bool)(object)value!)
                //                    ? new byte[] {0xff,0x00}
                //                    : new byte[] { 0x00, 0x00 };

                //        Write(address, bytes);

                //        return true;
                //    }
                case TypeCode.Int16:
                    {
                        var bytes = BitConverter.GetBytes((short)(object)value!);

                        Write(address, bytes, ModbusCommand.WriteOneRegister);

                        break;
                    }
                case TypeCode.Int32:
                    {
                        var bytes = BitConverter.GetBytes((int)(object)value!);
                        bytes = _byteFormatter.ByteTransDataFormat4(bytes);

                        Write(address, bytes, ModbusCommand.WriteRegister);

                        break;
                    }
                case TypeCode.Int64:
                    {
                        var bytes = BitConverter.GetBytes((long)(object)value!);
                        bytes = _byteFormatter.ByteTransDataFormat8(bytes);

                        Write(address, bytes, ModbusCommand.WriteRegister);

                        break;
                    }
                case TypeCode.Single:
                    {
                        var bytes = BitConverter.GetBytes((float)(object)value!);
                        bytes = _byteFormatter.ByteTransDataFormat4(bytes);

                        Write(address, bytes, ModbusCommand.WriteRegister);

                        break;
                    }
                case TypeCode.UInt16:
                    {
                        var bytes = BitConverter.GetBytes((ushort)(object)value!);

                        Write(address, bytes, ModbusCommand.WriteOneRegister);

                        break;
                    }
                case TypeCode.UInt32:
                    {
                        var bytes = BitConverter.GetBytes((uint)(object)value!);
                        bytes = _byteFormatter.ByteTransDataFormat4(bytes);

                        Write(address, bytes, ModbusCommand.WriteRegister);

                        break;
                    }
                case TypeCode.UInt64:
                    {
                        var bytes = BitConverter.GetBytes((ulong)(object)value!);
                        bytes = _byteFormatter.ByteTransDataFormat8(bytes);

                        Write(address, bytes, ModbusCommand.WriteRegister);

                        break;
                    }
                case TypeCode.Double:
                    {
                        var bytes = BitConverter.GetBytes((double)(object)value!);
                        bytes = _byteFormatter.ByteTransDataFormat8(bytes);

                        Write(address, bytes, ModbusCommand.WriteRegister);

                        break;
                    }
                case TypeCode.String:
                default:
                    throw new Exception("未识别的读取类型");
            }

            DisconnectServer();

            return true;
        }

        #endregion

        #region IDispose

        public void Dispose()
        {
            if (_tcpClient != null)
            {
                _tcpClient.Connected -= TcpClientOnConnected;
                _tcpClient.Disconnected -= TcpClientOnDisconnected;
                _tcpClient.Received -= TcpClientOnReceived;
            }

            _tcpClient?.Close();
            _tcpClient?.Dispose();
        }

        #endregion

        #region 私有

        /// <summary>
        /// TcpClient连接成功事件
        /// </summary>
        /// <param name="client"></param>
        /// <param name="e"></param>
        void TcpClientOnConnected(ITcpClient client, MsgEventArgs e)
        {

        }

        /// <summary>
        /// TcpClient连接断开事件
        /// </summary>
        /// <param name="client"></param>
        /// <param name="e"></param>
        void TcpClientOnDisconnected(ITcpClientBase client, ClientDisconnectedEventArgs e)
        {

        }

        /// <summary>
        /// TcpClient数据接收事件
        /// </summary>
        /// <param name="client"></param>
        /// <param name="byteBlock"></param>
        /// <param name="requestInfo"></param>
        void TcpClientOnReceived(TouchSocket.Sockets.TcpClient client, ByteBlock byteBlock, IRequestInfo requestInfo)
        {
            _receiveBuffer = new byte[byteBlock.CanReadLen];

            Array.Copy(byteBlock.Buffer, 0, _receiveBuffer, 0, _receiveBuffer.Length);

            var buffer = ReceiveFromSocket();
            buffer = buffer.TiaoTiaoArrayRemoveBegin(6);
            buffer = ExtractActualData(buffer);

            _finishReceive?.Invoke(buffer);

            _receiveLock.Set();
        }

        /// <summary>
        /// 统一的读取入口
        /// </summary>
        /// <param name="address"></param>
        /// <param name="length"></param>
        /// <param name="typeCode"></param>
        /// <exception cref="Exception"></exception>
        T[] Read<T>(string address, ushort length, TypeCode typeCode)
        {
            byte[] recBytes;

            switch (typeCode)
            {
                //case TypeCode.Boolean:
                case TypeCode.Int16:
                    {
                        recBytes = Read(address, GetWordLength(length, 1));

                        var tmp = TransToInt16(recBytes, 0, length);
                        return (T[])(object)tmp;
                    }
                case TypeCode.Int32:
                    {
                        recBytes = Read(address, GetWordLength(length, 2));

                        var tmp = TransInt32(recBytes, 0, length);
                        return (T[])(object)tmp;
                    }
                case TypeCode.Int64:
                    {
                        recBytes = Read(address, GetWordLength(length, 4));

                        var tmp = TransInt64(recBytes, 0, length);
                        return (T[])(object)tmp;
                    }
                case TypeCode.Single:
                    {
                        recBytes = Read(address, GetWordLength(length, 2));

                        var tmp = TransSingle(recBytes, 0, length);
                        return (T[])(object)tmp;
                    }
                case TypeCode.UInt16:
                    {
                        recBytes = Read(address, GetWordLength(length, 1));

                        var tmp = TransUInt16(recBytes, 0, length);
                        return (T[])(object)tmp;
                    }
                case TypeCode.UInt32:
                    {
                        recBytes = Read(address, GetWordLength(length, 2));

                        var tmp = TransUInt32(recBytes, 0, length);
                        return (T[])(object)tmp;
                    }
                case TypeCode.UInt64:
                    {
                        recBytes = Read(address, GetWordLength(length, 4));

                        var tmp = TransUInt64(recBytes, 0, length);
                        return (T[])(object)tmp;
                    }
                case TypeCode.Double:
                    {
                        recBytes = Read(address, GetWordLength(length, 4));

                        var tmp = TransDouble(recBytes, 0, length);
                        return (T[])(object)tmp;
                    }
                case TypeCode.String:
                default:
                    throw new Exception("未识别的读取类型");
            }
        }

        /// <summary>
        /// 拼指令读取
        /// </summary>
        /// <param name="address"></param>
        /// <param name="realLength"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        byte[] Read(string address, ushort realLength)
        {
            var cmds = CommandBuilder.BuildReadCommand(address, realLength, Station, AddressStartWithZero, ModbusCommand.ReadRegister);

            if (cmds.Length <= 0) throw new Exception("未能生成读取指令");

            var rec = new List<byte>();
            _finishReceive = new Action<byte[]>(t =>
            {
                rec.AddRange(t);
            });

            foreach (var cmd in cmds)
            {
                var send = PackCommandToTcp(cmd, (ushort)_incrementCount.GetCurrentValue());

                _tcpClient.Send(send);

                var b = _receiveLock.WaitOne(5000);
                if (!b)
                {
                    _finishReceive = null!;
                    throw new Exception("读取超时");
                }
            }

            _finishReceive = null!;

            return rec.ToArray();
        }

        void Write(string address, byte[] value, byte defaultFunc)
        {
            var cmd = CommandBuilder.BuildWriteCommand(address, value, Station, AddressStartWithZero, defaultFunc);
            cmd = PackCommandToTcp(cmd, (ushort)_incrementCount.GetCurrentValue());
            _tcpClient.Send(cmd);
        }

        /// <summary>
        /// 获取实际要读取的长度
        /// </summary>
        /// <param name="length"></param>
        /// <param name="dataTypeLength"></param>
        /// <returns></returns>
        ushort GetWordLength(int length, int dataTypeLength)
        {
            return (ushort)(length * dataTypeLength);
        }

        #endregion

        #region 数据接收

        byte[] ReceiveFromSocket()
        {
            var headBytes = Receive(8);

            var contentLenght = GetContentLengthByHeadBytes(headBytes);
            if (contentLenght == 0) return headBytes;

            var result = new byte[ProtocolHeadBytesLength + contentLenght];
            headBytes.CopyTo(result, 0);

            ReadBufferFromSocket(result, ProtocolHeadBytesLength, contentLenght);

            _receiveBuffer = null!;

            return result;
        }

        byte[] Receive(int receiveLength)
        {
            if (receiveLength <= 0) return new byte[0];

            var buffer = new byte[receiveLength];
            ReadBufferFromSocket(buffer, 0, receiveLength, true);

            return buffer;
        }

        void ReadBufferFromSocket(byte[] buffer, int offset, int length, bool forLock = false)
        {
            if (_receiveBuffer == null || _receiveBuffer.Length <= 0) throw new Exception("未接收到数据");

            var recCoun = 0;
            while (recCoun < length)
            {
                // 分割成16KB来接收数据
                int receive_length = Math.Min(length - recCoun, 1024 * 16);
                Array.Copy(_receiveBuffer, recCoun + offset, buffer, recCoun + offset, receive_length);
                recCoun += receive_length;
            }
        }

        #endregion

        byte[] PackCommandToTcp(byte[] modbus, ushort id)
        {
            byte[] buffer = new byte[modbus.Length + 6];
            buffer[0] = BitConverter.GetBytes(id)[1];
            buffer[1] = BitConverter.GetBytes(id)[0];

            buffer[4] = BitConverter.GetBytes(modbus.Length)[1];
            buffer[5] = BitConverter.GetBytes(modbus.Length)[0];

            modbus.CopyTo(buffer, 6);
            return buffer;
        }

        #region 接收数据验证

        int GetContentLengthByHeadBytes(byte[] headBytes)
        {
            if (headBytes?.Length >= ProtocolHeadBytesLength)
            {
                int length = headBytes[4] * 256 + headBytes[5];
                if (length == 0)
                {
                    byte[] buffer = new byte[ProtocolHeadBytesLength - 1];
                    for (int i = 0; i < buffer.Length; i++)
                    {
                        buffer[i] = headBytes[i + 1];
                    }
                    headBytes = buffer;
                    return headBytes[5] * 256 + headBytes[6] - 1;
                }
                else
                {
                    return Math.Min(length - 2, 300);
                }
            }
            else
                return 0;
        }

        /// <summary>
        /// 提取真实数据
        /// </summary>
        /// <param name="response"></param>
        /// <returns></returns>
        byte[] ExtractActualData(byte[] response)
        {
            if (response[1] >= 0x80) throw new Exception(GetDescriptionByErrorCode(response[2]));

            if (response.Length > 3) return response.TiaoTiaoArrayRemoveBegin(3);

            return new byte[0];
        }


        #endregion

        #region Error

        /// <summary>
        /// 不支持该功能码
        /// </summary>
        const byte FunctionCodeNotSupport = 0x01;
        /// <summary>
        /// 该地址越界
        /// </summary>
        const byte FunctionCodeOverBound = 0x02;
        /// <summary>
        /// 读取长度超过最大值
        /// </summary>
        const byte FunctionCodeQuantityOver = 0x03;
        /// <summary>
        /// 读写异常
        /// </summary>
        const byte FunctionCodeReadWriteException = 0x04;

        string ModbusTcpFunctionCodeNotSupport => "不支持的功能码";
        string ModbusTcpFunctionCodeOverBound => "读取的数据越界";
        string ModbusTcpFunctionCodeQuantityOver => "读取长度超过最大值";
        string ModbusTcpFunctionCodeReadWriteException => "读写异常";
        string UnknownError => "未知错误";

        string GetDescriptionByErrorCode(byte code)
        {
            switch (code)
            {
                case FunctionCodeNotSupport: return ModbusTcpFunctionCodeNotSupport;
                case FunctionCodeOverBound: return ModbusTcpFunctionCodeOverBound;
                case FunctionCodeQuantityOver: return ModbusTcpFunctionCodeQuantityOver;
                case FunctionCodeReadWriteException: return ModbusTcpFunctionCodeReadWriteException;
                default: return UnknownError;
            }
        }

        #endregion

        #region 数据转换

        short[] TransToInt16(byte[] buffer, int index, int length)
        {
            short[] tmp = new short[length];
            for (int i = 0; i < length; i++)
            {
                tmp[i] = TransInt16(buffer, index + 2 * i);
            }
            return tmp;
        }

        short TransInt16(byte[] buffer, int index)
        {
            if (IsInteger16Reverse)
            {
                byte[] tmp = new byte[2];
                tmp[0] = buffer[index + 1];
                tmp[1] = buffer[index + 0];
                return BitConverter.ToInt16(tmp, 0);
            }
            else
                return BitConverter.ToInt16(buffer, index);
        }

        int[] TransInt32(byte[] buffer, int index, int length)
        {
            int[] tmp = new int[length];
            for (int i = 0; i < length; i++)
            {
                tmp[i] = TransInt32(buffer, index + 4 * i);
            }
            return tmp;
        }

        int TransInt32(byte[] buffer, int index) => BitConverter.ToInt32(_byteFormatter.ByteTransDataFormat4(buffer, index), 0);

        long[] TransInt64(byte[] buffer, int index, int length)
        {
            long[] tmp = new long[length];
            for (int i = 0; i < length; i++)
            {
                tmp[i] = TransInt64(buffer, index + 8 * i);
            }
            return tmp;
        }

        long TransInt64(byte[] buffer, int index) => BitConverter.ToInt64(_byteFormatter.ByteTransDataFormat8(buffer, index), 0);

        float[] TransSingle(byte[] buffer, int index, int length)
        {
            float[] tmp = new float[length];
            for (int i = 0; i < length; i++)
            {
                tmp[i] = TransSingle(buffer, index + 4 * i);
            }
            return tmp;
        }

        float TransSingle(byte[] buffer, int index) => BitConverter.ToSingle(_byteFormatter.ByteTransDataFormat4(buffer, index), 0);

        ushort[] TransUInt16(byte[] buffer, int index, int length)
        {
            ushort[] tmp = new ushort[length];
            for (int i = 0; i < length; i++)
            {
                tmp[i] = TransUInt16(buffer, index + 2 * i);
            }
            return tmp;
        }

        ushort TransUInt16(byte[] buffer, int index) => BitConverter.ToUInt16(buffer, index);

        uint[] TransUInt32(byte[] buffer, int index, int length)
        {
            uint[] tmp = new uint[length];
            for (int i = 0; i < length; i++)
            {
                tmp[i] = TransUInt32(buffer, index + 4 * i);
            }
            return tmp;
        }

        uint TransUInt32(byte[] buffer, int index) => BitConverter.ToUInt32(_byteFormatter.ByteTransDataFormat4(buffer, index), 0);

        ulong[] TransUInt64(byte[] buffer, int index, int length)
        {
            ulong[] tmp = new ulong[length];
            for (int i = 0; i < length; i++)
            {
                tmp[i] = TransUInt64(buffer, index + 8 * i);
            }
            return tmp;
        }

        ulong TransUInt64(byte[] buffer, int index) => BitConverter.ToUInt64(_byteFormatter.ByteTransDataFormat8(buffer, index), 0);

        double[] TransDouble(byte[] buffer, int index, int length)
        {
            double[] tmp = new double[length];
            for (int i = 0; i < length; i++)
            {
                tmp[i] = TransDouble(buffer, index + 8 * i);
            }
            return tmp;
        }

        double TransDouble(byte[] buffer, int index) => BitConverter.ToDouble(_byteFormatter.ByteTransDataFormat8(buffer, index), 0);

        #endregion
    }
}
